﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Spear.Core.Dependency;

namespace Spear.Core.Helper
{
    /// <summary> 主键辅助类 </summary>
    public static class IdentityHelper
    {
        /// <summary> This algorithm generates secuential GUIDs across system boundaries, ideal for databases  </summary>
        /// <returns></returns>
        public static Guid NewSequentialGuid()
        {
            var uid = Guid.NewGuid().ToByteArray();
            var binDate = BitConverter.GetBytes(DateTime.Now.Ticks);

            var secuentialGuid = new byte[uid.Length];

            secuentialGuid[0] = uid[0];
            secuentialGuid[1] = uid[1];
            secuentialGuid[2] = uid[2];
            secuentialGuid[3] = uid[3];
            secuentialGuid[4] = uid[4];
            secuentialGuid[5] = uid[5];
            secuentialGuid[6] = uid[6];
            // set the first part of the 8th byte to '1100' so     
            // later we'll be able to validate it was generated by us   

            secuentialGuid[7] = (byte)(0xc0 | (0xf & uid[7]));

            // the last 8 bytes are sequential,    
            // it minimizes index fragmentation   
            // to a degree as long as there are not a large    
            // number of Secuential-Guids generated per millisecond  

            secuentialGuid[9] = binDate[0];
            secuentialGuid[8] = binDate[1];
            secuentialGuid[15] = binDate[2];
            secuentialGuid[14] = binDate[3];
            secuentialGuid[13] = binDate[4];
            secuentialGuid[12] = binDate[5];
            secuentialGuid[11] = binDate[6];
            secuentialGuid[10] = binDate[7];

            return new Guid(secuentialGuid);
        }

        /// <summary> Guid32位 </summary>
        public static string Guid32 => NewSequentialGuid().ToString("N");

        /// <summary> Guid16位 </summary>
        public static string Guid16
        {
            get
            {
                var i = Guid.NewGuid().ToByteArray().Aggregate<byte, long>(1, (current, b) => current * (b + 1));
                return $"{i - DateTimeOffset.Now.Ticks:x}";
            }
        }

        // 批量生成ID的时间刻度
        private const long TimeScaleLong = 100000000;
        // 批量生成ID的时间刻度
        private const long TimeScaleInt = 1000000000;
        // 生成ID的随机数长度
        private const int RandomLengthLong = 5;
        // 生成ID的随机数长度
        private const int RandomLengthInt = 3;
        // 计算时间间隔的开始时间
        private static readonly DateTime BeginDatetime = new DateTime(2013, 10, 1);
        // 随机数缓存
        private static readonly Dictionary<long, long> DictLong = new Dictionary<long, long>();
        // 随机数缓存
        private static readonly Dictionary<int, int> DictInt = new Dictionary<int, int>();
        // 时间戳缓存（上一次计算ID的系统时间按时间戳刻度取值）
        private static long _lastEndDatetimeTicksLong;
        // 时间戳缓存（上一次计算ID的系统时间按时间戳刻度取值）
        private static int _lastEndDatetimeTicksInt;
        // 静态随机数生成器
        private static Random _random;

        // 获取随机数
        private static long GetRandomLong(int length)
        {
            if (_random == null)
                _random = RandomHelper.Random();

            const int min = 0;
            var max = int.Parse(Math.Pow(10, length).ToString(CultureInfo.InvariantCulture));
            return long.Parse(_random.Next(min, max).ToString(CultureInfo.InvariantCulture));
        }

        /// <summary> 长整型ID </summary>
        public static long LongId
        {
            get
            {
                var timeStamp = (DateTime.Now.Ticks - BeginDatetime.Ticks) / TimeScaleLong;
                if (timeStamp != _lastEndDatetimeTicksLong)
                {
                    DictLong.Clear();
                }

                var power = long.Parse(Math.Pow(10, RandomLengthLong).ToString(CultureInfo.InvariantCulture));
                var rand = GetRandomLong(RandomLengthLong);
                var result = timeStamp * power + rand;

                if (DictLong.ContainsKey(result))
                {
                    var isRepeat = true;

                    for (var i = 0; i < power; i++)
                    {
                        rand = GetRandomLong(RandomLengthLong);
                        result = timeStamp * power + rand;

                        if (!DictLong.ContainsKey(result))
                        {
                            DictLong.Add(result, result);
                            isRepeat = false;
                            break;
                        }
                    }

                    if (isRepeat)
                    {
                        return 0L;
                    }
                }
                else
                {
                    DictLong.Add(result, result);
                }

                _lastEndDatetimeTicksLong = timeStamp;
                return result;
            }
        }
    }
}
